1//////////////////////////////////////////////////////////////////////
2// LibFile: transforms.scad
3// This is the file that the most commonly used transformations, distributors, and mutator are in.
4// To use, add the following lines to the beginning of your file:
5// ```
6// include <BOSL/constants.scad>
7// use <BOSL/transforms.scad>
8// ```
9//////////////////////////////////////////////////////////////////////
10
11/*
12BSD 2-Clause License
13
14Copyright (c) 2017, Revar Desmera
15All rights reserved.
16
17Redistribution and use in source and binary forms, with or without
18modification, are permitted provided that the following conditions are met:
19
20* Redistributions of source code must retain the above copyright notice, this
21 list of conditions and the following disclaimer.
22
23* Redistributions in binary form must reproduce the above copyright notice,
24 this list of conditions and the following disclaimer in the documentation
25 and/or other materials provided with the distribution.
26
27THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
28AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
30DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
31FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
33SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
34CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
35OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
36OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37*/
38
39
40use <math.scad>
41include <compat.scad>
42include <constants.scad>
43
44
45
46//////////////////////////////////////////////////////////////////////
47// Section: Translations
48//////////////////////////////////////////////////////////////////////
49
50
51// Module: move()
52//
53// Description:
54// Moves/translates children.
55//
56// Usage:
57// move([x], [y], [z]) ...
58// move([x,y,z]) ...
59//
60// Arguments:
61// x = X axis translation.
62// y = Y axis translation.
63// z = Z axis translation.
64//
65// Example:
66// #sphere(d=10);
67// move([0,20,30]) sphere(d=10);
68//
69// Example:
70// #sphere(d=10);
71// move(y=20) sphere(d=10);
72//
73// Example:
74// #sphere(d=10);
75// move(x=-10, y=-5) sphere(d=10);
76module move(a=[0,0,0], x=0, y=0, z=0)
77{
78 translate(a+[x,y,z]) children();
79}
80
81
82// Module: xmove()
83//
84// Description:
85// Moves/translates children the given amount along the X axis.
86//
87// Usage:
88// xmove(x) ...
89//
90// Arguments:
91// x = Amount to move right along the X axis. Negative values move left.
92//
93// Example:
94// #sphere(d=10);
95// xmove(20) sphere(d=10);
96module xmove(x=0) translate([x,0,0]) children();
97
98
99// Module: ymove()
100//
101// Description:
102// Moves/translates children the given amount along the Y axis.
103//
104// Usage:
105// ymove(y) ...
106//
107// Arguments:
108// y = Amount to move back along the Y axis. Negative values move forward.
109//
110// Example:
111// #sphere(d=10);
112// ymove(20) sphere(d=10);
113module ymove(y=0) translate([0,y,0]) children();
114
115
116// Module: zmove()
117//
118// Description:
119// Moves/translates children the given amount along the Z axis.
120//
121// Usage:
122// zmove(z) ...
123//
124// Arguments:
125// z = Amount to move up along the Z axis. Negative values move down.
126//
127// Example:
128// #sphere(d=10);
129// zmove(20) sphere(d=10);
130module zmove(z=0) translate([0,0,z]) children();
131
132
133// Module: left()
134//
135// Description:
136// Moves children left (in the X- direction) by the given amount.
137//
138// Usage:
139// left(x) ...
140//
141// Arguments:
142// x = Scalar amount to move left.
143//
144// Example:
145// #sphere(d=10);
146// left(20) sphere(d=10);
147module left(x=0) translate([-x,0,0]) children();
148
149
150// Module: right()
151//
152// Description:
153// Moves children right (in the X+ direction) by the given amount.
154//
155// Usage:
156// right(x) ...
157//
158// Arguments:
159// x = Scalar amount to move right.
160//
161// Example:
162// #sphere(d=10);
163// right(20) sphere(d=10);
164module right(x=0) translate([x,0,0]) children();
165
166
167// Module: fwd() / forward()
168//
169// Description:
170// Moves children forward (in the Y- direction) by the given amount.
171//
172// Usage:
173// fwd(y) ...
174// forward(y) ...
175//
176// Arguments:
177// y = Scalar amount to move forward.
178//
179// Example:
180// #sphere(d=10);
181// fwd(20) sphere(d=10);
182module forward(y=0) translate([0,-y,0]) children();
183module fwd(y=0) translate([0,-y,0]) children();
184
185
186// Module: back()
187//
188// Description:
189// Moves children back (in the Y+ direction) by the given amount.
190//
191// Usage:
192// back(y) ...
193//
194// Arguments:
195// y = Scalar amount to move back.
196//
197// Example:
198// #sphere(d=10);
199// back(20) sphere(d=10);
200module back(y=0) translate([0,y,0]) children();
201
202
203// Module: down()
204//
205// Description:
206// Moves children down (in the Z- direction) by the given amount.
207//
208// Usage:
209// down(z) ...
210//
211// Arguments:
212// z = Scalar amount to move down.
213//
214// Example:
215// #sphere(d=10);
216// down(20) sphere(d=10);
217module down(z=0) translate([0,0,-z]) children();
218
219
220// Module: up()
221//
222// Description:
223// Moves children up (in the Z+ direction) by the given amount.
224//
225// Usage:
226// up(z) ...
227//
228// Arguments:
229// z = Scalar amount to move up.
230//
231// Example:
232// #sphere(d=10);
233// up(20) sphere(d=10);
234module up(z=0) translate([0,0,z]) children();
235
236
237
238//////////////////////////////////////////////////////////////////////
239// Section: Rotations
240//////////////////////////////////////////////////////////////////////
241
242
243// Module: rot()
244//
245// Description:
246// Rotates children around an arbitrary axis by the given number of degrees.
247// Can be used as a drop-in replacement for `rotate()`, with extra features.
248//
249// Usage:
250// rot(a, [cp], [reverse]) ...
251// rot([X,Y,Z], [cp], [reverse]) ...
252// rot(a, v, [cp], [reverse]) ...
253// rot(from, to, [a], [reverse]) ...
254//
255// Arguments:
256// a = Scalar angle or vector of XYZ rotation angles to rotate by, in degrees.
257// v = vector for the axis of rotation. Default: [0,0,1] or V_UP
258// cp = centerpoint to rotate around. Default: [0,0,0]
259// from = Starting vector for vector-based rotations.
260// to = Target vector for vector-based rotations.
261// reverse = If true, exactly reverses the rotation, including axis rotation ordering. Default: false
262//
263// Example:
264// #cube([2,4,9]);
265// rot([30,60,0], cp=[0,0,9]) cube([2,4,9]);
266//
267// Example:
268// #cube([2,4,9]);
269// rot(30, v=[1,1,0], cp=[0,0,9]) cube([2,4,9]);
270//
271// Example:
272// #cube([2,4,9]);
273// rot(from=V_UP, to=V_LEFT+V_BACK) cube([2,4,9]);
274module rot(a=0, v=undef, cp=undef, from=undef, to=undef, reverse=false)
275{
276 if (is_def(cp)) {
277 translate(cp) rot(a=a, v=v, from=from, to=to, reverse=reverse) translate(-cp) children();
278 } else if (is_def(from)) {
279 assertion(is_def(to), "`from` and `to` should be used together.");
280 axis = vector_axis(from, to);
281 ang = vector_angle(from, to);
282 if (ang < 0.0001 && a == 0) {
283 children(); // May be slightly faster?
284 } else if (reverse) {
285 rotate(a=-ang, v=axis) rotate(a=-a, v=from) children();
286 } else {
287 rotate(a=ang, v=axis) rotate(a=a, v=from) children();
288 }
289 } else if (a == 0) {
290 children(); // May be slightly faster?
291 } else if (reverse) {
292 if (is_def(v)) {
293 rotate(a=-a, v=v) children();
294 } else if (is_scalar(a)) {
295 rotate(-a) children();
296 } else {
297 rotate([-a[0],0,0]) rotate([0,-a[1],0]) rotate([0,0,-a[2]]) children();
298 }
299 } else {
300 rotate(a=a, v=v) children();
301 }
302}
303
304
305// Module: xrot()
306//
307// Description:
308// Rotates children around the X axis by the given number of degrees.
309//
310// Usage:
311// xrot(a, [cp]) ...
312//
313// Arguments:
314// a = angle to rotate by in degrees.
315// cp = centerpoint to rotate around. Default: [0,0,0]
316//
317// Example:
318// #cylinder(h=50, r=10, center=true);
319// xrot(90) cylinder(h=50, r=10, center=true);
320module xrot(a=0, cp=undef)
321{
322 if (a==0) {
323 children(); // May be slightly faster?
324 } else if (is_def(cp)) {
325 translate(cp) rotate([a, 0, 0]) translate(-cp) children();
326 } else {
327 rotate([a, 0, 0]) children();
328 }
329}
330
331
332// Module: yrot()
333//
334// Description:
335// Rotates children around the Y axis by the given number of degrees.
336//
337// Usage:
338// yrot(a, [cp]) ...
339//
340// Arguments:
341// a = angle to rotate by in degrees.
342// cp = centerpoint to rotate around. Default: [0,0,0]
343//
344// Example:
345// #cylinder(h=50, r=10, center=true);
346// yrot(90) cylinder(h=50, r=10, center=true);
347module yrot(a=0, cp=undef)
348{
349 if (a==0) {
350 children(); // May be slightly faster?
351 } else if (is_def(cp)) {
352 translate(cp) rotate([0, a, 0]) translate(-cp) children();
353 } else {
354 rotate([0, a, 0]) children();
355 }
356}
357
358
359// Module: zrot()
360//
361// Description:
362// Rotates children around the Z axis by the given number of degrees.
363//
364// Usage:
365// zrot(a, [cp]) ...
366//
367// Arguments:
368// a = angle to rotate by in degrees.
369// cp = centerpoint to rotate around. Default: [0,0,0]
370//
371// Example:
372// #cube(size=[60,20,40], center=true);
373// zrot(90) cube(size=[60,20,40], center=true);
374module zrot(a=0, cp=undef)
375{
376 if (a==0) {
377 children(); // May be slightly faster?
378 } else if (is_def(cp)) {
379 translate(cp) rotate(a) translate(-cp) children();
380 } else {
381 rotate(a) children();
382 }
383}
384
385
386
387//////////////////////////////////////////////////////////////////////
388// Section: Scaling and Mirroring
389//////////////////////////////////////////////////////////////////////
390
391
392// Module: xscale()
393//
394// Description:
395// Scales children by the given factor on the X axis.
396//
397// Usage:
398// xscale(x) ...
399//
400// Arguments:
401// x = Factor to scale by along the X axis.
402//
403// Example:
404// xscale(3) sphere(r=10);
405module xscale(x) scale([x,1,1]) children();
406
407
408// Module: yscale()
409//
410// Description:
411// Scales children by the given factor on the Y axis.
412//
413// Usage:
414// yscale(y) ...
415//
416// Arguments:
417// y = Factor to scale by along the Y axis.
418//
419// Example:
420// yscale(3) sphere(r=10);
421module yscale(y) scale([1,y,1]) children();
422
423
424// Module: zscale()
425//
426// Description:
427// Scales children by the given factor on the Z axis.
428//
429// Usage:
430// zscale(z) ...
431//
432// Arguments:
433// z = Factor to scale by along the Z axis.
434//
435// Example:
436// zscale(3) sphere(r=10);
437module zscale(z) scale([1,1,z]) children();
438
439
440// Module: xflip()
441//
442// Description:
443// Mirrors the children along the X axis, like `mirror([1,0,0])` or `xscale(-1)`
444//
445// Usage:
446// xflip([cp]) ...
447//
448// Arguments:
449// cp = A point that lies on the plane of reflection.
450//
451// Example:
452// xflip() yrot(90) cylinder(d1=10, d2=0, h=20);
453// color("blue", 0.25) cube([0.01,15,15], center=true);
454// color("red", 0.333) yrot(90) cylinder(d1=10, d2=0, h=20);
455//
456// Example:
457// xflip(cp=[-5,0,0]) yrot(90) cylinder(d1=10, d2=0, h=20);
458// color("blue", 0.25) left(5) cube([0.01,15,15], center=true);
459// color("red", 0.333) yrot(90) cylinder(d1=10, d2=0, h=20);
460module xflip(cp=[0,0,0]) translate(cp) mirror([1,0,0]) translate(-cp) children();
461
462
463// Module: yflip()
464//
465// Description:
466// Mirrors the children along the Y axis, like `mirror([0,1,0])` or `yscale(-1)`
467//
468// Usage:
469// yflip([cp]) ...
470//
471// Arguments:
472// cp = A point that lies on the plane of reflection.
473//
474// Example:
475// yflip() xrot(90) cylinder(d1=10, d2=0, h=20);
476// color("blue", 0.25) cube([15,0.01,15], center=true);
477// color("red", 0.333) xrot(90) cylinder(d1=10, d2=0, h=20);
478//
479// Example:
480// yflip(cp=[0,5,0]) xrot(90) cylinder(d1=10, d2=0, h=20);
481// color("blue", 0.25) back(5) cube([15,0.01,15], center=true);
482// color("red", 0.333) xrot(90) cylinder(d1=10, d2=0, h=20);
483module yflip(cp=[0,0,0]) translate(cp) mirror([0,1,0]) translate(-cp) children();
484
485
486// Module: zflip()
487//
488// Description:
489// Mirrors the children along the Z axis, like `mirror([0,0,1])` or `zscale(-1)`
490//
491// Usage:
492// zflip([cp]) ...
493//
494// Arguments:
495// cp = A point that lies on the plane of reflection.
496//
497// Example:
498// zflip() cylinder(d1=10, d2=0, h=20);
499// color("blue", 0.25) cube([15,15,0.01], center=true);
500// color("red", 0.333) cylinder(d1=10, d2=0, h=20);
501//
502// Example:
503// zflip(cp=[0,0,-5]) cylinder(d1=10, d2=0, h=20);
504// color("blue", 0.25) down(5) cube([15,15,0.01], center=true);
505// color("red", 0.333) cylinder(d1=10, d2=0, h=20);
506module zflip(cp=[0,0,0]) translate(cp) mirror([0,0,1]) translate(-cp) children();
507
508
509
510//////////////////////////////////////////////////////////////////////
511// Section: Skewing
512//////////////////////////////////////////////////////////////////////
513
514
515// Module: skew_xy() / skew_z()
516//
517// Description:
518// Skews children on the X-Y plane, keeping constant in Z.
519//
520// Usage:
521// skew_xy([xa], [ya]) ...
522// skew_z([xa], [ya]) ...
523//
524// Arguments:
525// xa = skew angle towards the X direction.
526// ya = skew angle towards the Y direction.
527// planar = If true, this becomes a 2D operation.
528//
529// Example(FlatSpin):
530// #cube(size=10);
531// skew_xy(xa=30, ya=15) cube(size=10);
532// Example(2D):
533// skew_xy(xa=15,ya=30,planar=true) square(30);
534module skew_xy(xa=0, ya=0, planar=false) multmatrix(m = planar? matrix3_skew(xa, ya) : matrix4_skew_xy(xa, ya)) children();
535module zskew(xa=0, ya=0, planar=false) multmatrix(m = planar? matrix3_skew(xa, ya) : matrix4_skew_xy(xa, ya)) children();
536
537
538// Module: skew_yz() / skew_x()
539//
540// Description:
541// Skews children on the Y-Z plane, keeping constant in X.
542//
543// Usage:
544// skew_yz([ya], [za]) ...
545// skew_x([ya], [za]) ...
546//
547// Arguments:
548// ya = skew angle towards the Y direction.
549// za = skew angle towards the Z direction.
550//
551// Example(FlatSpin):
552// #cube(size=10);
553// skew_yz(ya=30, za=15) cube(size=10);
554module skew_yz(ya=0, za=0) multmatrix(m = matrix4_skew_yz(ya, za)) children();
555module xskew(ya=0, za=0) multmatrix(m = matrix4_skew_yz(ya, za)) children();
556
557
558// Module: skew_xz() / skew_y()
559//
560// Description:
561// Skews children on the X-Z plane, keeping constant in Y.
562//
563// Usage:
564// skew_xz([xa], [za]) ...
565// skew_y([xa], [za]) ...
566//
567// Arguments:
568// xa = skew angle towards the X direction.
569// za = skew angle towards the Z direction.
570//
571// Example(FlatSpin):
572// #cube(size=10);
573// skew_xz(xa=15, za=-10) cube(size=10);
574module skew_xz(xa=0, za=0) multmatrix(m = matrix4_skew_xz(xa, za)) children();
575module yskew(xa=0, za=0) multmatrix(m = matrix4_skew_xz(xa, za)) children();
576
577
578
579//////////////////////////////////////////////////////////////////////
580// Section: Translational Distributors
581//////////////////////////////////////////////////////////////////////
582
583
584// Module: place_copies()
585//
586// Description:
587// Makes copies of the given children at each of the given offsets.
588//
589// Usage:
590// place_copies(a) ...
591//
592// Arguments:
593// a = array of XYZ offset vectors. Default [[0,0,0]]
594//
595// Side Effects:
596// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
597//
598// Example:
599// #sphere(r=10);
600// place_copies([[-25,-25,0], [25,-25,0], [0,0,50], [0,25,0]]) sphere(r=10);
601module place_copies(a=[[0,0,0]])
602{
603 for (off = a) translate(off) children();
604}
605
606
607// Module: translate_copies()
608// Status: DEPRECATED, use `place_copies()` instead.
609//
610// Description:
611// Makes copies of the given children at each of the given offsets.
612//
613// Usage:
614// translate_copies(a) ...
615//
616// Arguments:
617// a = array of XYZ offset vectors. Default [[0,0,0]]
618//
619// Side Effects:
620// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
621module translate_copies(a=[[0,0,0]])
622{
623 deprecate("translate_copies()", "place_copies()");
624 place_copies(a) children();
625}
626
627
628// Module: line_of()
629// Status: DEPRECATED, use `spread(p1,p2)` instead
630//
631// Description:
632// Evenly distributes n duplicate children along an XYZ line.
633//
634// Usage:
635// line_of(p1, p2, [n]) ...
636//
637// Arguments:
638// p1 = starting point of line. (Default: [0,0,0])
639// p2 = ending point of line. (Default: [10,0,0])
640// n = number of copies to distribute along the line. (Default: 2)
641//
642// Side Effects:
643// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
644module line_of(p1=[0,0,0], p2=[10,0,0], n=2)
645{
646 deprecate("line_of()", "spread()");
647 spread(p1=p1, p2=p2, n=n) children();
648}
649
650
651
652// Module: spread()
653//
654// Description:
655// Evenly distributes `n` copies of all children along a line.
656// Copies every child at each position.
657//
658// Usage:
659// spread(l, [n], [p1]) ...
660// spread(l, spacing, [p1]) ...
661// spread(spacing, [n], [p1]) ...
662// spread(p1, p2, [n]) ...
663// spread(p1, p2, spacing) ...
664//
665// Arguments:
666// p1 = Starting point of line.
667// p2 = Ending point of line.
668// l = Length to spread copies over.
669// spacing = A 3D vector indicating which direction and distance to place each subsequent copy at.
670// n = Number of copies to distribute along the line. (Default: 2)
671//
672// Side Effects:
673// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
674// `$idx` is set to the index number of each child being copied.
675//
676// Example(FlatSpin):
677// spread([0,0,0], [5,5,20], n=6) cube(size=[3,2,1],center=true);
678// Examples:
679// spread(l=40, n=6) cube(size=[3,2,1],center=true);
680// spread(l=[15,30], n=6) cube(size=[3,2,1],center=true);
681// spread(l=40, spacing=10) cube(size=[3,2,1],center=true);
682// spread(spacing=[5,5,0], n=5) cube(size=[3,2,1],center=true);
683// Example:
684// spread(l=20, n=3) {
685// cube(size=[1,3,1],center=true);
686// cube(size=[3,1,1],center=true);
687// }
688module spread(p1, p2, spacing, l, n)
689{
690 ll = (
691 is_def(l)? scalar_vec3(l, 0) :
692 (is_def(spacing) && is_def(n))? (n * scalar_vec3(spacing, 0)) :
693 (is_def(p1) && is_def(p2))? point3d(p2-p1) :
694 undef
695 );
696 cnt = (
697 is_def(n)? n :
698 (is_def(spacing) && is_def(ll))? floor(norm(ll) / norm(scalar_vec3(spacing, 0)) + 1.000001) :
699 2
700 );
701 spc = (
702 !is_def(spacing)? (ll/(cnt-1)) :
703 is_scalar(spacing) && is_def(ll)? (ll/(cnt-1)) :
704 scalar_vec3(spacing, 0)
705 );
706 assertion(is_def(cnt), "Need two of `spacing`, 'l', 'n', or `p1`/`p2` arguments in `spread()`.");
707 spos = is_def(p1)? point3d(p1) : -(cnt-1)/2 * spc;
708 for (i=[0 : cnt-1]) {
709 pos = i * spc + spos;
710 $pos = pos;
711 $idx = i;
712 translate(pos) children();
713 }
714}
715
716
717// Module: xspread()
718//
719// Description:
720// Spreads out `n` copies of the children along a line on the X axis.
721//
722// Usage:
723// xspread(spacing, [n], [sp]) ...
724// xspread(l, [n], [sp]) ...
725//
726// Arguments:
727// spacing = spacing between copies. (Default: 1.0)
728// n = Number of copies to spread out. (Default: 2)
729// l = Length to spread copies over.
730// sp = If given, copies will be spread on a line to the right of starting position `sp`. If not given, copies will be spread along a line that is centered at [0,0,0].
731//
732// Side Effects:
733// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
734// `$idx` is set to the index number of each child being copied.
735//
736// Examples:
737// xspread(20) sphere(3);
738// xspread(20, n=3) sphere(3);
739// xspread(spacing=15, l=50) sphere(3);
740// xspread(n=4, l=30, sp=[0,10,0]) sphere(3);
741// Example:
742// xspread(10, n=3) {
743// cube(size=[1,3,1],center=true);
744// cube(size=[3,1,1],center=true);
745// }
746module xspread(spacing=undef, n=undef, l=undef, sp=undef)
747{
748 spread(
749 l=is_undef(l)? l : l*V_RIGHT,
750 spacing=is_undef(spacing)? spacing : spacing*V_RIGHT,
751 n=n, p1=sp
752 ) children();
753}
754
755
756// Module: yspread()
757//
758// Description:
759// Spreads out `n` copies of the children along a line on the Y axis.
760//
761// Usage:
762// yspread(spacing, [n], [sp]) ...
763// yspread(l, [n], [sp]) ...
764//
765// Arguments:
766// spacing = spacing between copies. (Default: 1.0)
767// n = Number of copies to spread out. (Default: 2)
768// l = Length to spread copies over.
769// sp = If given, copies will be spread on a line back from starting position `sp`. If not given, copies will be spread along a line that is centered at [0,0,0].
770//
771// Side Effects:
772// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
773// `$idx` is set to the index number of each child being copied.
774//
775// Examples:
776// yspread(20) sphere(3);
777// yspread(20, n=3) sphere(3);
778// yspread(spacing=15, l=50) sphere(3);
779// yspread(n=4, l=30, sp=[10,0,0]) sphere(3);
780// Example:
781// yspread(10, n=3) {
782// cube(size=[1,3,1],center=true);
783// cube(size=[3,1,1],center=true);
784// }
785module yspread(spacing=undef, n=undef, l=undef, sp=undef)
786{
787 spread(
788 l=is_undef(l)? l : l*V_BACK,
789 spacing=is_undef(spacing)? spacing : spacing*V_BACK,
790 n=n, p1=sp
791 ) children();
792}
793
794
795// Module: zspread()
796//
797// Description:
798// Spreads out `n` copies of the children along a line on the Z axis.
799//
800// Usage:
801// zspread(spacing, [n], [sp]) ...
802// zspread(l, [n], [sp]) ...
803//
804// Arguments:
805// spacing = spacing between copies. (Default: 1.0)
806// n = Number of copies to spread out. (Default: 2)
807// l = Length to spread copies over.
808// sp = If given, copies will be spread on a line up from starting position `sp`. If not given, copies will be spread along a line that is centered at [0,0,0].
809//
810// Side Effects:
811// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
812// `$idx` is set to the index number of each child being copied.
813//
814// Examples:
815// zspread(20) sphere(3);
816// zspread(20, n=3) sphere(3);
817// zspread(spacing=15, l=50) sphere(3);
818// zspread(n=4, l=30, sp=[10,0,0]) sphere(3);
819// Example:
820// zspread(10, n=3) {
821// cube(size=[1,3,1],center=true);
822// cube(size=[3,1,1],center=true);
823// }
824module zspread(spacing=undef, n=undef, l=undef, sp=undef)
825{
826 spread(
827 l=is_undef(l)? l : l*V_UP,
828 spacing=is_undef(spacing)? spacing : spacing*V_UP,
829 n=n, p1=sp
830 ) children();
831}
832
833
834
835// Module: distribute()
836//
837// Description:
838// Spreads out each individual child along the direction `dir`.
839// Every child is placed at a different position, in order.
840// This is useful for laying out groups of disparate objects
841// where you only really care about the spacing between them.
842//
843// Usage:
844// distribute(spacing, dir, [sizes]) ...
845// distribute(l, dir, [sizes]) ...
846//
847// Arguments:
848// spacing = Spacing to add between each child. (Default: 10.0)
849// sizes = Array containing how much space each child will need.
850// dir = Vector direction to distribute copies along.
851// l = Length to distribute copies along.
852//
853// Side Effect:
854// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
855// `$idx` is set to the index number of each child being copied.
856//
857// Example:
858// distribute(sizes=[100, 30, 50], dir=V_UP) {
859// sphere(r=50);
860// cube([10,20,30], center=true);
861// cylinder(d=30, h=50, center=true);
862// }
863module distribute(spacing=undef, sizes=undef, dir=V_RIGHT, l=undef)
864{
865 gaps = ($children < 2)? [0] :
866 is_def(sizes)? [for (i=[0:$children-2]) sizes[i]/2 + sizes[i+1]/2] :
867 [for (i=[0:$children-2]) 0];
868 spc = is_def(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10);
869 gaps2 = [for (gap = gaps) gap+spc];
870 spos = dir * -sum(gaps2)/2;
871 for (i=[0:$children-1]) {
872 totspc = sum(concat([0], slice(gaps2, 0, i)));
873 $pos = spos + totspc * dir;
874 $idx = i;
875 translate($pos) children(i);
876 }
877}
878
879
880// Module: xdistribute()
881//
882// Description:
883// Spreads out each individual child along the X axis.
884// Every child is placed at a different position, in order.
885// This is useful for laying out groups of disparate objects
886// where you only really care about the spacing between them.
887//
888// Usage:
889// xdistribute(spacing, [sizes]) ...
890// xdistribute(l, [sizes]) ...
891//
892// Arguments:
893// spacing = spacing between each child. (Default: 10.0)
894// sizes = Array containing how much space each child will need.
895// l = Length to distribute copies along.
896//
897// Side Effect:
898// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
899// `$idx` is set to the index number of each child being copied.
900//
901// Example:
902// xdistribute(sizes=[100, 10, 30], spacing=40) {
903// sphere(r=50);
904// cube([10,20,30], center=true);
905// cylinder(d=30, h=50, center=true);
906// }
907module xdistribute(spacing=10, sizes=undef, l=undef)
908{
909 dir = V_RIGHT;
910 gaps = ($children < 2)? [0] :
911 is_def(sizes)? [for (i=[0:$children-2]) sizes[i]/2 + sizes[i+1]/2] :
912 [for (i=[0:$children-2]) 0];
913 spc = is_def(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10);
914 gaps2 = [for (gap = gaps) gap+spc];
915 spos = dir * -sum(gaps2)/2;
916 for (i=[0:$children-1]) {
917 totspc = sum(concat([0], slice(gaps2, 0, i)));
918 $pos = spos + totspc * dir;
919 $idx = i;
920 translate($pos) children(i);
921 }
922}
923
924
925// Module: ydistribute()
926//
927// Description:
928// Spreads out each individual child along the Y axis.
929// Every child is placed at a different position, in order.
930// This is useful for laying out groups of disparate objects
931// where you only really care about the spacing between them.
932//
933// Usage:
934// ydistribute(spacing, [sizes])
935// ydistribute(l, [sizes])
936//
937// Arguments:
938// spacing = spacing between each child. (Default: 10.0)
939// sizes = Array containing how much space each child will need.
940// l = Length to distribute copies along.
941//
942// Side Effect:
943// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
944// `$idx` is set to the index number of each child being copied.
945//
946// Example:
947// ydistribute(sizes=[30, 20, 100], spacing=40) {
948// cylinder(d=30, h=50, center=true);
949// cube([10,20,30], center=true);
950// sphere(r=50);
951// }
952module ydistribute(spacing=10, sizes=undef, l=undef)
953{
954 dir = V_BACK;
955 gaps = ($children < 2)? [0] :
956 is_def(sizes)? [for (i=[0:$children-2]) sizes[i]/2 + sizes[i+1]/2] :
957 [for (i=[0:$children-2]) 0];
958 spc = is_def(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10);
959 gaps2 = [for (gap = gaps) gap+spc];
960 spos = dir * -sum(gaps2)/2;
961 for (i=[0:$children-1]) {
962 totspc = sum(concat([0], slice(gaps2, 0, i)));
963 $pos = spos + totspc * dir;
964 $idx = i;
965 translate($pos) children(i);
966 }
967}
968
969
970// Module: zdistribute()
971//
972// Description:
973// Spreads out each individual child along the Z axis.
974// Every child is placed at a different position, in order.
975// This is useful for laying out groups of disparate objects
976// where you only really care about the spacing between them.
977//
978// Usage:
979// zdistribute(spacing, [sizes])
980// zdistribute(l, [sizes])
981//
982// Arguments:
983// spacing = spacing between each child. (Default: 10.0)
984// sizes = Array containing how much space each child will need.
985// l = Length to distribute copies along.
986//
987// Side Effect:
988// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
989// `$idx` is set to the index number of each child being copied.
990//
991// Example:
992// zdistribute(sizes=[30, 20, 100], spacing=40) {
993// cylinder(d=30, h=50, center=true);
994// cube([10,20,30], center=true);
995// sphere(r=50);
996// }
997module zdistribute(spacing=10, sizes=undef, l=undef)
998{
999 dir = V_UP;
1000 gaps = ($children < 2)? [0] :
1001 is_def(sizes)? [for (i=[0:$children-2]) sizes[i]/2 + sizes[i+1]/2] :
1002 [for (i=[0:$children-2]) 0];
1003 spc = is_def(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10);
1004 gaps2 = [for (gap = gaps) gap+spc];
1005 spos = dir * -sum(gaps2)/2;
1006 for (i=[0:$children-1]) {
1007 totspc = sum(concat([0], slice(gaps2, 0, i)));
1008 $pos = spos + totspc * dir;
1009 $idx = i;
1010 translate($pos) children(i);
1011 }
1012}
1013
1014
1015
1016// Module: grid2d()
1017//
1018// Description:
1019// Makes a square or hexagonal grid of copies of children.
1020//
1021// Usage:
1022// grid2d(size, spacing, [stagger], [scale], [in_poly], [orient], [align]) ...
1023// grid2d(size, cols, rows, [stagger], [scale], [in_poly], [orient], [align]) ...
1024// grid2d(spacing, cols, rows, [stagger], [scale], [in_poly], [orient], [align]) ...
1025// grid2d(spacing, in_poly, [stagger], [scale], [orient], [align]) ...
1026// grid2d(cols, rows, in_poly, [stagger], [scale], [orient], [align]) ...
1027//
1028// Arguments:
1029// size = The [X,Y] size to spread the copies over.
1030// spacing = Distance between copies in [X,Y] or scalar distance.
1031// cols = How many columns of copies to make. If staggered, count both staggered and unstaggered columns.
1032// rows = How many rows of copies to make. If staggered, count both staggered and unstaggered rows.
1033// stagger = If true, make a staggered (hexagonal) grid. If false, make square grid. If "alt", makes alternate staggered pattern. Default: false
1034// scale = [X,Y] scaling factors to reshape grid.
1035// in_poly = If given a list of polygon points, only creates copies whose center would be inside the polygon. Polygon can be concave and/or self crossing.
1036// orient = Orientation axis for the grid. Orientation is NOT applied to individual children.
1037// align = Alignment of the grid. Alignment is NOT applies to individual children.
1038//
1039// Side Effects:
1040// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
1041// `$col` is set to the integer column number for each child.
1042// `$row` is set to the integer row number for each child.
1043//
1044// Examples:
1045// grid2d(size=50, spacing=10, stagger=false) cylinder(d=10, h=1);
1046// grid2d(spacing=10, rows=7, cols=13, stagger=true) cylinder(d=6, h=5);
1047// grid2d(spacing=10, rows=7, cols=13, stagger="alt") cylinder(d=6, h=5);
1048// grid2d(size=50, rows=11, cols=11, stagger=true) cylinder(d=5, h=1);
1049//
1050// Example:
1051// poly = [[-25,-25], [25,25], [-25,25], [25,-25]];
1052// grid2d(spacing=5, stagger=true, in_poly=poly)
1053// zrot(180/6) cylinder(d=5, h=1, $fn=6);
1054// %polygon(poly);
1055//
1056// Example:
1057// // Makes a grid of hexagon pillars whose tops are all angled
1058// // to reflect light at [0,0,50], if they were reflective.
1059// use <BOSL/math.scad>
1060// hexregion = [for (a = [0:60:359.9]) 50.01*[cos(a), sin(a)]];
1061// grid2d(spacing=10, stagger=true, in_poly=hexregion) {
1062// // Note: You must use for(var=[val]) or let(var=val)
1063// // to set vars from $pos or other special vars in this scope.
1064// let (ref_v = (normalize([0,0,50]-point3d($pos)) + V_UP)/2)
1065// half_of(v=-ref_v, cp=[0,0,5])
1066// zrot(180/6)
1067// cylinder(h=20, d=10/cos(180/6)+0.01, $fn=6);
1068// }
1069module grid2d(size=undef, spacing=undef, cols=undef, rows=undef, stagger=false, scale=[1,1,1], in_poly=undef, orient=ORIENT_Z, align=V_CENTER)
1070{
1071 assert_in_list("stagger", stagger, [false, true, "alt"]);
1072 scl = vmul(scalar_vec3(scale, 1), (stagger!=false? [0.5, sin(60), 0] : [1,1,0]));
1073 if (is_def(size)) {
1074 siz = scalar_vec3(size);
1075 if (is_def(spacing)) {
1076 spc = vmul(scalar_vec3(spacing), scl);
1077 maxcols = ceil(siz[0]/spc[0]);
1078 maxrows = ceil(siz[1]/spc[1]);
1079 grid2d(spacing=spacing, cols=maxcols, rows=maxrows, stagger=stagger, scale=scale, in_poly=in_poly, orient=orient, align=align) children();
1080 } else {
1081 spc = [siz[0]/cols, siz[1]/rows, 0];
1082 grid2d(spacing=spc, cols=cols, rows=rows, stagger=stagger, scale=scale, in_poly=in_poly, orient=orient, align=align) children();
1083 }
1084 } else {
1085 spc = is_array(spacing)? spacing : vmul(scalar_vec3(spacing), scl);
1086 bounds = is_def(in_poly)? pointlist_bounds(in_poly) : undef;
1087 bnds = is_def(bounds)? [for (a=[0:1]) 2*max(vabs([ for (i=[0,1]) bounds[i][a] ]))+1 ] : undef;
1088 mcols = is_def(cols)? cols : (is_def(spc) && is_def(bnds))? quantup(ceil(bnds[0]/spc[0])-1, 4)+1 : undef;
1089 mrows = is_def(rows)? rows : (is_def(spc) && is_def(bnds))? quantup(ceil(bnds[1]/spc[1])-1, 4)+1 : undef;
1090 siz = vmul(spc, [mcols-1, mrows-1, 0]);
1091 staggermod = (stagger == "alt")? 1 : 0;
1092 if (stagger == false) {
1093 orient_and_align(siz, orient, align) {
1094 for (row = [0:mrows-1]) {
1095 for (col = [0:mcols-1]) {
1096 pos = [col*spc[0], row*spc[1]] - point2d(siz/2);
1097 if (!is_def(in_poly) || point_in_polygon(pos, in_poly)>=0) {
1098 $col = col;
1099 $row = row;
1100 $pos = pos;
1101 translate(pos) rot(orient,reverse=true) children();
1102 }
1103 }
1104 }
1105 }
1106 } else {
1107 // stagger == true or stagger == "alt"
1108 orient_and_align(siz, orient, align) {
1109 cols1 = ceil(mcols/2);
1110 cols2 = mcols - cols1;
1111 for (row = [0:mrows-1]) {
1112 rowcols = ((row%2) == staggermod)? cols1 : cols2;
1113 if (rowcols > 0) {
1114 for (col = [0:rowcols-1]) {
1115 rowdx = (row%2 != staggermod)? spc[0] : 0;
1116 pos = [2*col*spc[0]+rowdx, row*spc[1]] - point2d(siz/2);
1117 if (!is_def(in_poly) || point_in_polygon(pos, in_poly)>=0) {
1118 $col = col * 2 + ((row%2!=staggermod)? 1 : 0);
1119 $row = row;
1120 $pos = pos;
1121 translate(pos) rot(orient,reverse=true) children();
1122 }
1123 }
1124 }
1125 }
1126 }
1127 }
1128 }
1129}
1130
1131
1132
1133// Module: grid3d()
1134//
1135// Description:
1136// Makes a 3D grid of duplicate children.
1137//
1138// Usage:
1139// grid3d(n, spacing) ...
1140// grid3d(n=[Xn,Yn,Zn], spacing=[dX,dY,dZ]) ...
1141// grid3d([xa], [ya], [za]) ...
1142//
1143// Arguments:
1144// xa = array or range of X-axis values to offset by. (Default: [0])
1145// ya = array or range of Y-axis values to offset by. (Default: [0])
1146// za = array or range of Z-axis values to offset by. (Default: [0])
1147// n = Optional number of copies to have per axis.
1148// spacing = spacing of copies per axis. Use with `n`.
1149//
1150// Side Effect:
1151// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
1152// `$idx` is set to the [Xidx,Yidx,Zidx] index values of each child copy, when using `count` and `n`.
1153//
1154// Examples(FlatSpin):
1155// grid3d(xa=[0:25:50],ya=[0,40],za=[-20:40:20]) sphere(r=5);
1156// grid3d(n=[3, 4, 2], spacing=[60, 50, 40]) sphere(r=10);
1157// Examples:
1158// grid3d(ya=[-60:40:60],za=[0,70]) sphere(r=10);
1159// grid3d(n=3, spacing=30) sphere(r=10);
1160// grid3d(n=[3, 1, 2], spacing=30) sphere(r=10);
1161// grid3d(n=[3, 4], spacing=[80, 60]) sphere(r=10);
1162// Examples:
1163// grid3d(n=[10, 10, 10], spacing=50) color($idx/9) cube(50, center=true);
1164module grid3d(xa=[0], ya=[0], za=[0], n=undef, spacing=undef)
1165{
1166 n = scalar_vec3(n, 1);
1167 spacing = scalar_vec3(spacing, undef);
1168 if (is_def(n) && is_def(spacing)) {
1169 for (xi = [0:n.x-1]) {
1170 for (yi = [0:n.y-1]) {
1171 for (zi = [0:n.z-1]) {
1172 $idx = [xi,yi,zi];
1173 $pos = vmul(spacing, $idx - (n-[1,1,1])/2);
1174 translate($pos) children();
1175 }
1176 }
1177 }
1178 } else {
1179 for (xoff = xa, yoff = ya, zoff = za) {
1180 $pos = [xoff, yoff, zoff];
1181 translate($pos) children();
1182 }
1183 }
1184}
1185
1186
1187
1188// Module: grid_of()
1189// Status: DEPRECATED, use `grid3d()` instead.
1190//
1191// Description:
1192// Makes a 3D grid of duplicate children.
1193//
1194// Usage:
1195// grid_of(n, spacing) ...
1196// grid_of(n=[Xn,Yn,Zn], spacing=[dX,dY,dZ]) ...
1197// grid_of([xa], [ya], [za]) ...
1198//
1199// Arguments:
1200// xa = array or range of X-axis values to offset by. (Default: [0])
1201// ya = array or range of Y-axis values to offset by. (Default: [0])
1202// za = array or range of Z-axis values to offset by. (Default: [0])
1203// n = Optional number of copies to have per axis.
1204// spacing = spacing of copies per axis. Use with `n`.
1205//
1206// Side Effect:
1207// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
1208// `$idx` is set to the [Xidx,Yidx,Zidx] index values of each child copy, when using `count` and `n`.
1209module grid_of(xa=[0], ya=[0], za=[0], count=undef, spacing=undef)
1210{
1211 deprecate("grid_of()", "grid3d()");
1212 grid3d(xa=xa, ya=ya, za=za, n=count, spacing=spacing) children();
1213}
1214
1215
1216
1217//////////////////////////////////////////////////////////////////////
1218// Section: Rotational Distributors
1219//////////////////////////////////////////////////////////////////////
1220
1221
1222// Module: rot_copies()
1223//
1224// Description:
1225// Given a number of XYZ rotation angles, or a list of angles and an axis `v`,
1226// rotates copies of the children to each of those angles.
1227//
1228// Usage:
1229// rot_copies(rots, [cp], [sa], [delta], [subrot]) ...
1230// rot_copies(rots, v, [cp], [sa], [delta], [subrot]) ...
1231// rot_copies(n, [v], [cp], [sa], [delta], [subrot]) ...
1232//
1233// Arguments:
1234// rots = A list of [X,Y,Z] rotation angles in degrees. If `v` is given, this will be a list of scalar angles in degrees to rotate around `v`.
1235// v = If given, this is the vector to rotate around.
1236// cp = Centerpoint to rotate around.
1237// n = Optional number of evenly distributed copies, rotated around the ring. If given, overrides `rots` argument.
1238// sa = Starting angle, in degrees. For use with `n`. Angle is in degrees counter-clockwise.
1239// delta = [X,Y,Z] amount to move away from cp before rotating. Makes rings of copies.
1240// subrot = If false, don't sub-rotate children as they are copied around the ring.
1241//
1242// Side Effects:
1243// `$ang` is set to the rotation angle (or XYZ rotation triplet) of each child copy, and can be used to modify each child individually.
1244// `$idx` is set to the index value of each child copy.
1245//
1246// Example:
1247// #cylinder(h=20, r1=5, r2=0);
1248// rot_copies([[45,0,0],[0,45,90],[90,-45,270]]) cylinder(h=20, r1=5, r2=0);
1249//
1250// Example:
1251// rot_copies([45, 90, 135], v=V_DOWN+V_BACK)
1252// yrot(90) cylinder(h=20, r1=5, r2=0);
1253// color("red",0.333) yrot(90) cylinder(h=20, r1=5, r2=0);
1254//
1255// Example:
1256// rot_copies(n=6, v=V_DOWN+V_BACK)
1257// yrot(90) cylinder(h=20, r1=5, r2=0);
1258// color("red",0.333) yrot(90) cylinder(h=20, r1=5, r2=0);
1259//
1260// Example:
1261// rot_copies(n=6, v=V_DOWN+V_BACK, delta=[10,0,0])
1262// yrot(90) cylinder(h=20, r1=5, r2=0);
1263// color("red",0.333) yrot(90) cylinder(h=20, r1=5, r2=0);
1264//
1265// Example:
1266// rot_copies(n=6, v=V_UP+V_FWD, delta=[10,0,0], sa=45)
1267// yrot(90) cylinder(h=20, r1=5, r2=0);
1268// color("red",0.333) yrot(90) cylinder(h=20, r1=5, r2=0);
1269//
1270// Example:
1271// rot_copies(n=6, v=V_DOWN+V_BACK, delta=[20,0,0], subrot=false)
1272// yrot(90) cylinder(h=20, r1=5, r2=0);
1273// color("red",0.333) yrot(90) cylinder(h=20, r1=5, r2=0);
1274module rot_copies(rots=[], v=undef, cp=[0,0,0], count=undef, n=undef, sa=0, offset=0, delta=[0,0,0], subrot=true)
1275{
1276 cnt = first_defined([count, n]);
1277 sang = sa + offset;
1278 angs = is_def(cnt)? (cnt<=0? [] : [for (i=[0:cnt-1]) i/cnt*360+sang]) : rots;
1279 if (cp != [0,0,0]) {
1280 translate(cp) rot_copies(rots=rots, v=v, n=cnt, sa=sang, delta=delta, subrot=subrot) children();
1281 } else if (subrot) {
1282 for ($idx = [0:len(angs)-1]) {
1283 $ang = angs[$idx];
1284 rotate(a=$ang,v=v) translate(delta) rot(a=sang,v=v,reverse=true) children();
1285 }
1286 } else {
1287 for ($idx = [0:len(angs)-1]) {
1288 $ang = angs[$idx];
1289 rotate(a=$ang,v=v) translate(delta) rot(a=$ang,v=v,reverse=true) children();
1290 }
1291 }
1292}
1293
1294
1295// Module: xrot_copies()
1296//
1297// Description:
1298// Given an array of angles, rotates copies of the children
1299// to each of those angles around the X axis.
1300//
1301// Usage:
1302// xrot_copies(rots, [r], [cp], [sa], [subrot]) ...
1303// xrot_copies(n, [r], [cp], [sa], [subrot]) ...
1304//
1305// Arguments:
1306// rots = Optional array of rotation angles, in degrees, to make copies at.
1307// cp = Centerpoint to rotate around.
1308// n = Optional number of evenly distributed copies to be rotated around the ring. If given, overrides `rots` argument.
1309// sa = Starting angle, in degrees. For use with `n`. Angle is in degrees counter-clockwise from Y+, when facing the origin from X+. First unrotated copy is placed at that angle.
1310// r = Radius to move children back, away from cp, before rotating. Makes rings of copies.
1311// subrot = If false, don't sub-rotate children as they are copied around the ring.
1312//
1313// Side Effects:
1314// `$ang` is set to the rotation angle of each child copy, and can be used to modify each child individually.
1315//
1316// Example:
1317// xrot_copies([180, 270, 315])
1318// cylinder(h=20, r1=5, r2=0);
1319// color("red",0.333) cylinder(h=20, r1=5, r2=0);
1320//
1321// Example:
1322// xrot_copies(n=6)
1323// cylinder(h=20, r1=5, r2=0);
1324// color("red",0.333) cylinder(h=20, r1=5, r2=0);
1325//
1326// Example:
1327// xrot_copies(n=6, r=10)
1328// xrot(-90) cylinder(h=20, r1=5, r2=0);
1329// color("red",0.333) xrot(-90) cylinder(h=20, r1=5, r2=0);
1330//
1331// Example:
1332// xrot_copies(n=6, r=10, sa=45)
1333// xrot(-90) cylinder(h=20, r1=5, r2=0);
1334// color("red",0.333) xrot(-90) cylinder(h=20, r1=5, r2=0);
1335//
1336// Example:
1337// xrot_copies(n=6, r=20, subrot=false)
1338// xrot(-90) cylinder(h=20, r1=5, r2=0, center=true);
1339// color("red",0.333) xrot(-90) cylinder(h=20, r1=5, r2=0, center=true);
1340module xrot_copies(rots=[], cp=[0,0,0], n=undef, count=undef, sa=0, offset=0, r=0, subrot=true)
1341{
1342 cnt = first_defined([count, n]);
1343 sang = sa + offset;
1344 rot_copies(rots=rots, v=V_RIGHT, cp=cp, n=cnt, sa=sang, delta=[0, r, 0], subrot=subrot) children();
1345}
1346
1347
1348// Module: yrot_copies()
1349//
1350// Description:
1351// Given an array of angles, rotates copies of the children
1352// to each of those angles around the Y axis.
1353//
1354// Usage:
1355// yrot_copies(rots, [r], [cp], [sa], [subrot]) ...
1356// yrot_copies(n, [r], [cp], [sa], [subrot]) ...
1357//
1358// Arguments:
1359// rots = Optional array of rotation angles, in degrees, to make copies at.
1360// cp = Centerpoint to rotate around.
1361// n = Optional number of evenly distributed copies to be rotated around the ring. If given, overrides `rots` argument.
1362// sa = Starting angle, in degrees. For use with `n`. Angle is in degrees counter-clockwise from X-, when facing the origin from Y+.
1363// r = Radius to move children left, away from cp, before rotating. Makes rings of copies.
1364// subrot = If false, don't sub-rotate children as they are copied around the ring.
1365//
1366// Side Effects:
1367// `$ang` is set to the rotation angle of each child copy, and can be used to modify each child individually.
1368//
1369// Example:
1370// yrot_copies([180, 270, 315])
1371// cylinder(h=20, r1=5, r2=0);
1372// color("red",0.333) cylinder(h=20, r1=5, r2=0);
1373//
1374// Example:
1375// yrot_copies(n=6)
1376// cylinder(h=20, r1=5, r2=0);
1377// color("red",0.333) cylinder(h=20, r1=5, r2=0);
1378//
1379// Example:
1380// yrot_copies(n=6, r=10)
1381// yrot(-90) cylinder(h=20, r1=5, r2=0);
1382// color("red",0.333) yrot(-90) cylinder(h=20, r1=5, r2=0);
1383//
1384// Example:
1385// yrot_copies(n=6, r=10, sa=45)
1386// yrot(-90) cylinder(h=20, r1=5, r2=0);
1387// color("red",0.333) yrot(-90) cylinder(h=20, r1=5, r2=0);
1388//
1389// Example:
1390// yrot_copies(n=6, r=20, subrot=false)
1391// yrot(-90) cylinder(h=20, r1=5, r2=0, center=true);
1392// color("red",0.333) yrot(-90) cylinder(h=20, r1=5, r2=0, center=true);
1393module yrot_copies(rots=[], cp=[0,0,0], n=undef, count=undef, sa=0, offset=0, r=0, subrot=true)
1394{
1395 cnt = first_defined([count, n]);
1396 sang = sa + offset;
1397 rot_copies(rots=rots, v=V_BACK, cp=cp, n=cnt, sa=sang, delta=[-r, 0, 0], subrot=subrot) children();
1398}
1399
1400
1401// Module: zrot_copies()
1402//
1403// Description:
1404// Given an array of angles, rotates copies of the children
1405// to each of those angles around the Z axis.
1406//
1407// Usage:
1408// zrot_copies(rots, [r], [cp], [sa], [subrot]) ...
1409// zrot_copies(n, [r], [cp], [sa], [subrot]) ...
1410//
1411// Arguments:
1412// rots = Optional array of rotation angles, in degrees, to make copies at.
1413// cp = Centerpoint to rotate around.
1414// n = Optional number of evenly distributed copies to be rotated around the ring. If given, overrides `rots` argument.
1415// sa = Starting angle, in degrees. For use with `n`. Angle is in degrees counter-clockwise from X+, when facing the origin from Z+.
1416// r = Radius to move children right, away from cp, before rotating. Makes rings of copies.
1417// subrot = If false, don't sub-rotate children as they are copied around the ring.
1418//
1419// Side Effects:
1420// `$ang` is set to the rotation angle of each child copy, and can be used to modify each child individually.
1421//
1422// Example:
1423// zrot_copies([180, 270, 315])
1424// yrot(90) cylinder(h=20, r1=5, r2=0);
1425// color("red",0.333) yrot(90) cylinder(h=20, r1=5, r2=0);
1426//
1427// Example:
1428// zrot_copies(n=6)
1429// yrot(90) cylinder(h=20, r1=5, r2=0);
1430// color("red",0.333) yrot(90) cylinder(h=20, r1=5, r2=0);
1431//
1432// Example:
1433// zrot_copies(n=6, r=10)
1434// yrot(90) cylinder(h=20, r1=5, r2=0);
1435// color("red",0.333) yrot(90) cylinder(h=20, r1=5, r2=0);
1436//
1437// Example:
1438// zrot_copies(n=6, r=20, sa=45)
1439// yrot(90) cylinder(h=20, r1=5, r2=0, center=true);
1440// color("red",0.333) yrot(90) cylinder(h=20, r1=5, r2=0, center=true);
1441//
1442// Example:
1443// zrot_copies(n=6, r=20, subrot=false)
1444// yrot(-90) cylinder(h=20, r1=5, r2=0, center=true);
1445// color("red",0.333) yrot(-90) cylinder(h=20, r1=5, r2=0, center=true);
1446module zrot_copies(rots=[], cp=[0,0,0], n=undef, count=undef, sa=0, offset=0, r=0, subrot=true)
1447{
1448 cnt = first_defined([count, n]);
1449 sang = sa + offset;
1450 rot_copies(rots=rots, v=V_UP, cp=cp, n=cnt, sa=sang, delta=[r, 0, 0], subrot=subrot) children();
1451}
1452
1453
1454// Module: xring()
1455//
1456// Description:
1457// Distributes `n` copies of the given children on a circle of radius `r`
1458// around the X axis. If `rot` is true, each copy is rotated in place to keep
1459// the same side towards the center. The first, unrotated copy will be at the
1460// starting angle `sa`.
1461//
1462// Usage:
1463// xring(n, r, [sa], [cp], [rot]) ...
1464//
1465// Arguments:
1466// n = Number of copies of children to distribute around the circle. (Default: 2)
1467// r = Radius of ring to distribute children around. (Default: 0)
1468// sa = Start angle for first (unrotated) copy. (Default: 0)
1469// cp = Centerpoint of ring. Default: [0,0,0]
1470// rot = If true, rotate each copy to keep the same side towards the center of the ring. Default: true.
1471//
1472// Side Effects:
1473// `$ang` is set to the rotation angle of each child copy, and can be used to modify each child individually.
1474// `$idx` is set to the index value of each child copy.
1475//
1476// Examples:
1477// xring(n=6, r=10) xrot(-90) cylinder(h=20, r1=5, r2=0);
1478// xring(n=6, r=10, sa=45) xrot(-90) cylinder(h=20, r1=5, r2=0);
1479// xring(n=6, r=20, rot=false) cylinder(h=20, r1=6, r2=0, center=true);
1480module xring(n=2, r=0, sa=0, cp=[0,0,0], rot=true)
1481{
1482 xrot_copies(count=n, r=r, sa=sa, cp=cp, subrot=rot) children();
1483}
1484
1485
1486// Module: yring()
1487//
1488// Description:
1489// Distributes `n` copies of the given children on a circle of radius `r`
1490// around the Y axis. If `rot` is true, each copy is rotated in place to keep
1491// the same side towards the center. The first, unrotated copy will be at the
1492// starting angle `sa`.
1493//
1494// Usage:
1495// yring(n, r, [sa], [cp], [rot]) ...
1496//
1497// Arguments:
1498// n = Number of copies of children to distribute around the circle. (Default: 2)
1499// r = Radius of ring to distribute children around. (Default: 0)
1500// sa = Start angle for first (unrotated) copy. (Default: 0)
1501// cp = Centerpoint of ring. Default: [0,0,0]
1502// rot = If true, rotate each copy to keep the same side towards the center of the ring. Default: true.
1503//
1504// Side Effects:
1505// `$ang` is set to the rotation angle of each child copy, and can be used to modify each child individually.
1506// `$idx` is set to the index value of each child copy.
1507//
1508// Examples:
1509// yring(n=6, r=10) yrot(-90) cylinder(h=20, r1=5, r2=0);
1510// yring(n=6, r=10, sa=45) yrot(-90) cylinder(h=20, r1=5, r2=0);
1511// yring(n=6, r=20, rot=false) cylinder(h=20, r1=6, r2=0, center=true);
1512module yring(n=2, r=0, sa=0, cp=[0,0,0], rot=true)
1513{
1514 yrot_copies(count=n, r=r, sa=sa, cp=cp, subrot=rot) children();
1515}
1516
1517
1518// Module: zring()
1519//
1520// Description:
1521// Distributes `n` copies of the given children on a circle of radius `r`
1522// around the Z axis. If `rot` is true, each copy is rotated in place to keep
1523// the same side towards the center. The first, unrotated copy will be at the
1524// starting angle `sa`.
1525//
1526// Usage:
1527// zring(r, n, [sa], [cp], [rot]) ...
1528//
1529// Arguments:
1530// n = Number of copies of children to distribute around the circle. (Default: 2)
1531// r = Radius of ring to distribute children around. (Default: 0)
1532// sa = Start angle for first (unrotated) copy. (Default: 0)
1533// cp = Centerpoint of ring. Default: [0,0,0]
1534// rot = If true, rotate each copy to keep the same side towards the center of the ring. Default: true.
1535//
1536// Side Effects:
1537// `$ang` is set to the relative angle from `cp` of each child copy, and can be used to modify each child individually.
1538// `$idx` is set to the index value of each child copy.
1539//
1540// Examples:
1541// zring(n=6, r=10) yrot(90) cylinder(h=20, r1=5, r2=0);
1542// zring(n=6, r=10, sa=45) yrot(90) cylinder(h=20, r1=5, r2=0);
1543// zring(n=6, r=20, rot=false) yrot(90) cylinder(h=20, r1=6, r2=0, center=true);
1544module zring(n=2, r=0, sa=0, cp=[0,0,0], rot=true)
1545{
1546 zrot_copies(count=n, r=r, sa=sa, cp=cp, subrot=rot) children();
1547}
1548
1549
1550// Module: arc_of()
1551//
1552// Description:
1553// Evenly distributes n duplicate children around an ovoid arc on the XY plane.
1554//
1555// Usage:
1556// arc_of(r|d, n, [sa], [ea], [rot]
1557// arc_of(rx|dx, ry|dy, n, [sa], [ea], [rot]
1558//
1559// Arguments:
1560// n = number of copies to distribute around the circle. (Default: 6)
1561// r = radius of circle (Default: 1)
1562// rx = radius of ellipse on X axis. Used instead of r.
1563// ry = radius of ellipse on Y axis. Used instead of r.
1564// d = diameter of circle. (Default: 2)
1565// dx = diameter of ellipse on X axis. Used instead of d.
1566// dy = diameter of ellipse on Y axis. Used instead of d.
1567// rot = whether to rotate the copied children. (Default: false)
1568// sa = starting angle. (Default: 0.0)
1569// ea = ending angle. Will distribute copies CCW from sa to ea. (Default: 360.0)
1570//
1571// Side Effects:
1572// `$ang` is set to the rotation angle of each child copy, and can be used to modify each child individually.
1573// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
1574// `$idx` is set to the index value of each child copy.
1575//
1576// Example:
1577// #cube(size=[10,3,3],center=true);
1578// arc_of(d=40, n=5) cube(size=[10,3,3],center=true);
1579//
1580// Example:
1581// #cube(size=[10,3,3],center=true);
1582// arc_of(d=40, n=5, sa=45, ea=225) cube(size=[10,3,3],center=true);
1583//
1584// Example:
1585// #cube(size=[10,3,3],center=true);
1586// arc_of(r=15, n=8, rot=false) cube(size=[10,3,3],center=true);
1587//
1588// Example:
1589// #cube(size=[10,3,3],center=true);
1590// arc_of(rx=20, ry=10, n=8) cube(size=[10,3,3],center=true);
1591module arc_of(
1592 n=6,
1593 r=undef, rx=undef, ry=undef,
1594 d=undef, dx=undef, dy=undef,
1595 sa=0, ea=360,
1596 rot=true
1597) {
1598 rx = get_radius(rx, r, dx, d, 1);
1599 ry = get_radius(ry, r, dy, d, 1);
1600 sa = posmod(sa, 360);
1601 ea = posmod(ea, 360);
1602 n = (abs(ea-sa)<0.01)?(n+1):n;
1603 delt = (((ea<=sa)?360.0:0)+ea-sa)/(n-1);
1604 for ($idx = [0:n-1]) {
1605 $ang = sa + ($idx * delt);
1606 $pos =[rx*cos($ang), ry*sin($ang), 0];
1607 translate($pos) {
1608 zrot(rot? atan2(ry*sin($ang), rx*cos($ang)) : 0) {
1609 children();
1610 }
1611 }
1612 }
1613}
1614
1615
1616
1617// Module: ovoid_spread()
1618//
1619// Description:
1620// Spreads children semi-evenly over the surface of a sphere.
1621//
1622// Usage:
1623// ovoid_spread(r|d, n, [cone_ang], [scale], [perp]) ...
1624//
1625// Arguments:
1626// r = Radius of the sphere to distribute over
1627// d = Diameter of the sphere to distribute over
1628// n = How many copies to evenly spread over the surface.
1629// cone_ang = Angle of the cone, in degrees, to limit how much of the sphere gets covered. For full sphere coverage, use 180. Measured pre-scaling. Default: 180
1630// scale = The [X,Y,Z] scaling factors to reshape the sphere being covered.
1631// perp = If true, rotate children to be perpendicular to the sphere surface. Default: true
1632//
1633// Side Effects:
1634// `$pos` is set to the relative post-scaled centerpoint of each child copy, and can be used to modify each child individually.
1635// `$theta` is set to the theta angle of the child from the center of the sphere.
1636// `$phi` is set to the pre-scaled phi angle of the child from the center of the sphere.
1637// `$rad` is set to the pre-scaled radial distance of the child from the center of the sphere.
1638// `$idx` is set to the index number of each child being copied.
1639//
1640// Example:
1641// ovoid_spread(n=250, d=100, cone_ang=45, scale=[3,3,1])
1642// cylinder(d=10, h=10, center=false);
1643//
1644// Example:
1645// ovoid_spread(n=500, d=100, cone_ang=180)
1646// color(normalize(point3d(vabs($pos))))
1647// cylinder(d=8, h=10, center=false);
1648module ovoid_spread(r=undef, d=undef, n=100, cone_ang=90, scale=[1,1,1], perp=true)
1649{
1650 r = get_radius(r=r, d=d, dflt=50);
1651 cnt = ceil(n / (cone_ang/180));
1652
1653 // Calculate an array of [theta,phi] angles for `n` number of
1654 // points, almost evenly spaced across the surface of a sphere.
1655 // This approximation is based on the golden spiral method.
1656 theta_phis = [for (x=[0:n-1]) [180*(1+sqrt(5))*(x+0.5)%360, acos(1-2*(x+0.5)/cnt)]];
1657
1658 for ($idx = [0:len(theta_phis)-1]) {
1659 tp = theta_phis[$idx];
1660 xyz = spherical_to_xyz(r, tp[0], tp[1]);
1661 $pos = vmul(xyz,scale);
1662 $theta = tp[0];
1663 $phi = tp[1];
1664 $rad = r;
1665 translate($pos) {
1666 if (perp) {
1667 rot(from=V_UP, to=xyz) children();
1668 } else {
1669 children();
1670 }
1671 }
1672 }
1673}
1674
1675
1676
1677//////////////////////////////////////////////////////////////////////
1678// Section: Reflectional Distributors
1679//////////////////////////////////////////////////////////////////////
1680
1681
1682// Module: mirror_copy()
1683//
1684// Description:
1685// Makes a copy of the children, mirrored across the given plane.
1686//
1687// Usage:
1688// mirror_copy(v, [cp], [offset]) ...
1689//
1690// Arguments:
1691// v = The normal vector of the plane to mirror across.
1692// offset = distance to offset away from the plane.
1693// cp = A point that lies on the mirroring plane.
1694//
1695// Side Effects:
1696// `$orig` is true for the original instance of children. False for the copy.
1697// `$idx` is set to the index value of each copy.
1698//
1699// Example:
1700// mirror_copy([1,-1,0]) zrot(-45) yrot(90) cylinder(d1=10, d2=0, h=20);
1701// color("blue",0.25) zrot(-45) cube([0.01,15,15], center=true);
1702//
1703// Example:
1704// mirror_copy([1,1,0], offset=5) rot(a=90,v=[-1,1,0]) cylinder(d1=10, d2=0, h=20);
1705// color("blue",0.25) zrot(45) cube([0.01,15,15], center=true);
1706//
1707// Example:
1708// mirror_copy(V_UP+V_BACK, cp=[0,-5,-5]) rot(from=V_UP, to=V_BACK+V_UP) cylinder(d1=10, d2=0, h=20);
1709// color("blue",0.25) translate([0,-5,-5]) rot(from=V_UP, to=V_BACK+V_UP) cube([15,15,0.01], center=true);
1710module mirror_copy(v=[0,0,1], offset=0, cp=[0,0,0])
1711{
1712 nv = v/norm(v);
1713 off = nv*offset;
1714 if (cp == [0,0,0]) {
1715 translate(off) {
1716 $orig = true;
1717 $idx = 0;
1718 children();
1719 }
1720 mirror(nv) translate(off) {
1721 $orig = false;
1722 $idx = 1;
1723 children();
1724 }
1725 } else {
1726 translate(off) children();
1727 translate(cp) mirror(nv) translate(-cp) translate(off) children();
1728 }
1729}
1730
1731
1732// Module: xflip_copy()
1733//
1734// Description:
1735// Makes a copy of the children, mirrored across the X axis.
1736//
1737// Usage:
1738// xflip_copy([cp], [offset]) ...
1739//
1740// Arguments:
1741// offset = Distance to offset children right, before copying.
1742// cp = A point that lies on the mirroring plane.
1743//
1744// Side Effects:
1745// `$orig` is true for the original instance of children. False for the copy.
1746// `$idx` is set to the index value of each copy.
1747//
1748// Example:
1749// xflip_copy() yrot(90) cylinder(h=20, r1=4, r2=0);
1750// color("blue",0.25) cube([0.01,15,15], center=true);
1751//
1752// Example:
1753// xflip_copy(offset=5) yrot(90) cylinder(h=20, r1=4, r2=0);
1754// color("blue",0.25) cube([0.01,15,15], center=true);
1755//
1756// Example:
1757// xflip_copy(cp=[-5,0,0]) yrot(90) cylinder(h=20, r1=4, r2=0);
1758// color("blue",0.25) left(5) cube([0.01,15,15], center=true);
1759module xflip_copy(offset=0, cp=[0,0,0])
1760{
1761 mirror_copy(v=[1,0,0], offset=offset, cp=cp) children();
1762}
1763
1764
1765// Module: yflip_copy()
1766//
1767// Description:
1768// Makes a copy of the children, mirrored across the Y axis.
1769//
1770// Usage:
1771// yflip_copy([cp], [offset]) ...
1772//
1773// Arguments:
1774// offset = Distance to offset children back, before copying.
1775// cp = A point that lies on the mirroring plane.
1776//
1777// Side Effects:
1778// `$orig` is true for the original instance of children. False for the copy.
1779// `$idx` is set to the index value of each copy.
1780//
1781// Example:
1782// yflip_copy() xrot(-90) cylinder(h=20, r1=4, r2=0);
1783// color("blue",0.25) cube([15,0.01,15], center=true);
1784//
1785// Example:
1786// yflip_copy(offset=5) xrot(-90) cylinder(h=20, r1=4, r2=0);
1787// color("blue",0.25) cube([15,0.01,15], center=true);
1788//
1789// Example:
1790// yflip_copy(cp=[0,-5,0]) xrot(-90) cylinder(h=20, r1=4, r2=0);
1791// color("blue",0.25) fwd(5) cube([15,0.01,15], center=true);
1792module yflip_copy(offset=0, cp=[0,0,0])
1793{
1794 mirror_copy(v=[0,1,0], offset=offset, cp=cp) children();
1795}
1796
1797
1798// Module: zflip_copy()
1799//
1800// Description:
1801// Makes a copy of the children, mirrored across the Z axis.
1802//
1803// Usage:
1804// zflip_copy([cp], [offset]) ...
1805// `$idx` is set to the index value of each copy.
1806//
1807// Arguments:
1808// offset = Distance to offset children up, before copying.
1809// cp = A point that lies on the mirroring plane.
1810//
1811// Side Effects:
1812// `$orig` is true for the original instance of children. False for the copy.
1813//
1814// Example:
1815// zflip_copy() cylinder(h=20, r1=4, r2=0);
1816// color("blue",0.25) cube([15,15,0.01], center=true);
1817//
1818// Example:
1819// zflip_copy(offset=5) cylinder(h=20, r1=4, r2=0);
1820// color("blue",0.25) cube([15,15,0.01], center=true);
1821//
1822// Example:
1823// zflip_copy(cp=[0,0,-5]) cylinder(h=20, r1=4, r2=0);
1824// color("blue",0.25) down(5) cube([15,15,0.01], center=true);
1825module zflip_copy(offset=0, cp=[0,0,0])
1826{
1827 mirror_copy(v=[0,0,1], offset=offset, cp=cp) children();
1828}
1829
1830
1831//////////////////////////////////////////////////////////////////////
1832// Section: Mutators
1833//////////////////////////////////////////////////////////////////////
1834
1835
1836// Module: half_of()
1837//
1838// Usage:
1839// half_of(v, [cp], [s]) ...
1840//
1841// Description:
1842// Slices an object at a cut plane, and masks away everything that is on one side.
1843//
1844// Arguments:
1845// v = Normal of plane to slice at. Keeps everything on the side the normal points to. Default: [0,0,1] (V_UP)
1846// cp = If given as a scalar, moves the cut plane along the normal by the given amount. If given as a point, specifies a point on the cut plane. This can be used to shift where it slices the object at. Default: [0,0,0]
1847// s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, it messes with centering your view. Default: 100
1848// planar = If true, this becomes a 2D operation. When planar, a `v` of `V_UP` or `V_DOWN` becomes equivalent of `V_BACK` and `V_FWD` respectively.
1849//
1850// Examples:
1851// half_of(V_DOWN+V_BACK, cp=[0,-10,0]) cylinder(h=40, r1=10, r2=0, center=false);
1852// half_of(V_DOWN+V_LEFT, s=200) sphere(d=150);
1853// Example(2D):
1854// half_of([1,1], planar=true) circle(d=50);
1855module half_of(v=V_UP, cp=[0,0,0], s=100, planar=false)
1856{
1857 cp = is_scalar(cp)? cp*normalize(v) : cp;
1858 if (cp != [0,0,0]) {
1859 translate(cp) half_of(v=v, s=s, planar=planar) translate(-cp) children();
1860 } else if (planar) {
1861 v = (v==V_UP)? V_BACK : (v==V_DOWN)? V_FWD : v;
1862 ang = atan2(v.y, v.x);
1863 difference() {
1864 children();
1865 rotate(ang+90) {
1866 back(s/2) square(s, center=true);
1867 }
1868 }
1869 } else {
1870 difference() {
1871 children();
1872 rot(from=V_UP, to=-v) {
1873 up(s/2) cube(s, center=true);
1874 }
1875 }
1876 }
1877}
1878
1879
1880// Module: top_half()
1881//
1882// Usage:
1883// top_half([z|cp], [s]) ...
1884//
1885// Description:
1886// Slices an object at a horizontal X-Y cut plane, and masks away everything that is below it.
1887//
1888// Arguments:
1889// cp = If given as a scalar, moves the cut plane up by the given amount. If given as a point, specifies a point on the cut plane. Default: [0,0,0]
1890// z = The Z coordinate of the cut-plane, if given. Use instead of `cp`.
1891// s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, it messes with centering your view. Default: 100
1892// planar = If true, this becomes equivalent to a planar `back_half()`.
1893//
1894// Examples(Spin):
1895// top_half() sphere(r=20);
1896// top_half(z=5) sphere(r=20);
1897// top_half(cp=5) sphere(r=20);
1898// top_half(cp=[0,0,-8]) sphere(r=20);
1899// Example(2D):
1900// top_half(planar=true) circle(r=20);
1901module top_half(s=100, z=undef, cp=[0,0,0], planar=false)
1902{
1903 dir = planar? V_BACK : V_UP;
1904 cp = is_scalar(z)? [0,0,z] : is_scalar(cp)? cp*dir : cp;
1905 translate(cp) difference() {
1906 translate(-cp) children();
1907 translate(-dir*s/2) {
1908 if (planar) {
1909 square(s, center=true);
1910 } else {
1911 cube(s, center=true);
1912 }
1913 }
1914 }
1915}
1916
1917
1918
1919// Module: bottom_half()
1920//
1921// Usage:
1922// bottom_half([z|cp], [s]) ...
1923//
1924// Description:
1925// Slices an object at a horizontal X-Y cut plane, and masks away everything that is above it.
1926//
1927// Arguments:
1928// cp = If given as a scalar, moves the cut plane down by the given amount. If given as a point, specifies a point on the cut plane. Default: [0,0,0]
1929// z = The Z coordinate of the cut-plane, if given. Use instead of `cp`.
1930// s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, it messes with centering your view. Default: 100
1931// planar = If true, this becomes equivalent to a planar `front_half()`.
1932//
1933// Examples:
1934// bottom_half() sphere(r=20);
1935// bottom_half(z=-10) sphere(r=20);
1936// bottom_half(cp=-10) sphere(r=20);
1937// bottom_half(cp=[0,0,10]) sphere(r=20);
1938// Example(2D):
1939// bottom_half(planar=true) circle(r=20);
1940module bottom_half(s=100, z=undef, cp=[0,0,0], planar=false)
1941{
1942 dir = planar? V_FWD : V_DOWN;
1943 cp = is_scalar(z)? [0,0,z] : is_scalar(cp)? cp*dir : cp;
1944 translate(cp) difference() {
1945 translate(-cp) children();
1946 translate(-dir*s/2) {
1947 if (planar) {
1948 square(s, center=true);
1949 } else {
1950 cube(s, center=true);
1951 }
1952 }
1953 }
1954}
1955
1956
1957
1958// Module: left_half()
1959//
1960// Usage:
1961// left_half([x|cp], [s]) ...
1962//
1963// Description:
1964// Slices an object at a vertical Y-Z cut plane, and masks away everything that is right of it.
1965//
1966// Arguments:
1967// cp = If given as a scalar, moves the cut plane left by the given amount. If given as a point, specifies a point on the cut plane. Default: [0,0,0]
1968// x = The X coordinate of the cut-plane, if given. Use instead of `cp`.
1969// s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, it messes with centering your view. Default: 100
1970// planar = If true, this becomes a 2D operation.
1971//
1972// Examples:
1973// left_half() sphere(r=20);
1974// left_half(x=-8) sphere(r=20);
1975// left_half(cp=-8) sphere(r=20);
1976// left_half(cp=[8,0,0]) sphere(r=20);
1977// Example(2D):
1978// left_half(planar=true) circle(r=20);
1979module left_half(s=100, x=undef, cp=[0,0,0], planar=false)
1980{
1981 dir = V_LEFT;
1982 cp = is_scalar(x)? [x,0,0] : is_scalar(cp)? cp*dir : cp;
1983 translate(cp) difference() {
1984 translate(-cp) children();
1985 translate(-dir*s/2) {
1986 if (planar) {
1987 square(s, center=true);
1988 } else {
1989 cube(s, center=true);
1990 }
1991 }
1992 }
1993}
1994
1995
1996
1997// Module: right_half()
1998//
1999// Usage:
2000// right_half([x|cp], [s]) ...
2001//
2002// Description:
2003// Slices an object at a vertical Y-Z cut plane, and masks away everything that is left of it.
2004//
2005// Arguments:
2006// cp = If given as a scalar, moves the cut plane right by the given amount. If given as a point, specifies a point on the cut plane. Default: [0,0,0]
2007// x = The X coordinate of the cut-plane, if given. Use instead of `cp`.
2008// s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, it messes with centering your view. Default: 100
2009// planar = If true, this becomes a 2D operation.
2010//
2011// Examples(FlatSpin):
2012// right_half() sphere(r=20);
2013// right_half(x=-5) sphere(r=20);
2014// right_half(cp=-5) sphere(r=20);
2015// right_half(cp=[-5,0,0]) sphere(r=20);
2016// Example(2D):
2017// right_half(planar=true) circle(r=20);
2018module right_half(s=100, x=undef, cp=[0,0,0], planar=false)
2019{
2020 dir = V_RIGHT;
2021 cp = is_scalar(x)? [x,0,0] : is_scalar(cp)? cp*dir : cp;
2022 translate(cp) difference() {
2023 translate(-cp) children();
2024 translate(-dir*s/2) {
2025 if (planar) {
2026 square(s, center=true);
2027 } else {
2028 cube(s, center=true);
2029 }
2030 }
2031 }
2032}
2033
2034
2035
2036// Module: front_half()
2037//
2038// Usage:
2039// front_half([y|cp], [s]) ...
2040//
2041// Description:
2042// Slices an object at a vertical X-Z cut plane, and masks away everything that is behind it.
2043//
2044// Arguments:
2045// cp = If given as a scalar, moves the cut plane forward by the given amount. If given as a point, specifies a point on the cut plane. Default: [0,0,0]
2046// y = The Y coordinate of the cut-plane, if given. Use instead of `cp`.
2047// s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, it messes with centering your view. Default: 100
2048// planar = If true, this becomes a 2D operation.
2049//
2050// Examples(FlatSpin):
2051// front_half() sphere(r=20);
2052// front_half(y=5) sphere(r=20);
2053// front_half(cp=5) sphere(r=20);
2054// front_half(cp=[0,5,0]) sphere(r=20);
2055// Example(2D):
2056// front_half(planar=true) circle(r=20);
2057module front_half(s=100, y=undef, cp=[0,0,0], planar=false)
2058{
2059 dir = V_FWD;
2060 cp = is_scalar(y)? [0,y,0] : is_scalar(cp)? cp*dir : cp;
2061 translate(cp) difference() {
2062 translate(-cp) children();
2063 translate(-dir*s/2) {
2064 if (planar) {
2065 square(s, center=true);
2066 } else {
2067 cube(s, center=true);
2068 }
2069 }
2070 }
2071}
2072
2073
2074
2075// Module: back_half()
2076//
2077// Usage:
2078// back_half([y|cp], [s]) ...
2079//
2080// Description:
2081// Slices an object at a vertical X-Z cut plane, and masks away everything that is in front of it.
2082//
2083// Arguments:
2084// cp = If given as a scalar, moves the cut plane back by the given amount. If given as a point, specifies a point on the cut plane. Default: [0,0,0]
2085// y = The Y coordinate of the cut-plane, if given. Use instead of `cp`.
2086// s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, it messes with centering your view. Default: 100
2087// planar = If true, this becomes a 2D operation.
2088//
2089// Examples:
2090// back_half() sphere(r=20);
2091// back_half(y=8) sphere(r=20);
2092// back_half(cp=8) sphere(r=20);
2093// back_half(cp=[0,-10,0]) sphere(r=20);
2094// Example(2D):
2095// back_half(planar=true) circle(r=20);
2096module back_half(s=100, y=undef, cp=[0,0,0], planar=false)
2097{
2098 dir = V_BACK;
2099 cp = is_scalar(y)? [0,y,0] : is_scalar(cp)? cp*dir : cp;
2100 translate(cp) difference() {
2101 translate(-cp) children();
2102 translate(-dir*s/2) {
2103 if (planar) {
2104 square(s, center=true);
2105 } else {
2106 cube(s, center=true);
2107 }
2108 }
2109 }
2110}
2111
2112
2113
2114// Module: chain_hull()
2115//
2116// Usage:
2117// chain_hull() ...
2118//
2119// Description:
2120// Performs hull operations between consecutive pairs of children,
2121// then unions all of the hull results. This can be a very slow
2122// operation, but it can provide results that are hard to get
2123// otherwise.
2124//
2125// Example:
2126// chain_hull() {
2127// cube(5, center=true);
2128// translate([30, 0, 0]) sphere(d=15);
2129// translate([60, 30, 0]) cylinder(d=10, h=20);
2130// translate([60, 60, 0]) cube([10,1,20], center=false);
2131// }
2132module chain_hull()
2133{
2134 union() {
2135 if ($children == 1) {
2136 children();
2137 } else if ($children > 1) {
2138 for (i =[1:$children-1]) {
2139 hull() {
2140 children(i-1);
2141 children(i);
2142 }
2143 }
2144 }
2145 }
2146}
2147
2148
2149// Module: extrude_arc()
2150//
2151// Description:
2152// Extrudes 2D shapes around a partial circle arc, with optional rounded caps.
2153// This is mostly useful for backwards compatability with older OpenSCAD versions
2154// without the `angle` argument in rotate_extrude.
2155//
2156// Usage:
2157// extrude_arc(arc, r|d, [sa], [caps], [orient], [align], [masksize]) ...
2158//
2159// Arguments:
2160// arc = Number of degrees to traverse.
2161// sa = Start angle in degrees.
2162// r = Radius of arc.
2163// d = Diameter of arc.
2164// orient = The axis to align to. Use `ORIENT_` constants from `constants.scad`
2165// align = The side of the origin the part should be aligned with. Use `V_` constants from `constants.scad`
2166// masksize = size of mask used to clear unused part of circle arc. should be larger than height or width of 2D shapes to extrude.
2167// caps = If true, spin the 2D shapes to make rounded caps the ends of the arc.
2168// convexity = Max number of times a ray passes through the 2D shape's walls.
2169//
2170// Example(Med):
2171// pts=[[-5/2, -5], [-5/2, 0], [-5/2-3, 5], [5/2+3, 5], [5/2, 0], [5/2, -5]];
2172// #polygon(points=pts);
2173// extrude_arc(arc=270, sa=45, r=40, caps=true, convexity=4, $fa=2, $fs=2) {
2174// polygon(points=pts);
2175// }
2176module extrude_arc(arc=90, sa=0, r=undef, d=undef, orient=ORIENT_Z, align=V_CENTER, masksize=100, caps=false, convexity=4)
2177{
2178 eps = 0.001;
2179 r = get_radius(r=r, d=d, dflt=100);
2180 orient_and_align([2*r, 2*r, 0], orient, align) {
2181 zrot(sa) {
2182 if (caps) {
2183 place_copies([[r,0,0], cylindrical_to_xyz(r, arc, 0)]) {
2184 rotate_extrude(convexity=convexity) {
2185 difference() {
2186 children();
2187 left(masksize/2) square(masksize, center=true);
2188 }
2189 }
2190 }
2191 }
2192 difference() {
2193 rotate_extrude(angle=arc, convexity=convexity*2) {
2194 right(r) {
2195 children();
2196 }
2197 }
2198 if(version_num() < 20190000) {
2199 maxd = r + masksize;
2200 if (arc<180) rotate(arc) back(maxd/2) cube([2*maxd, maxd, masksize+0.1], center=true);
2201 difference() {
2202 fwd(maxd/2) cube([2*maxd, maxd, masksize+0.2], center=true);
2203 if (arc>180) rotate(arc-180) back(maxd/2) cube([2*maxd, maxd, masksize+0.1], center=true);
2204 }
2205 }
2206 }
2207 }
2208 }
2209}
2210
2211
2212//////////////////////////////////////////////////////////////////////
2213// Section: 2D Mutators
2214//////////////////////////////////////////////////////////////////////
2215
2216
2217// Module: round2d()
2218// Usage:
2219// round2d(r) ...
2220// round2d(or) ...
2221// round2d(ir) ...
2222// round2d(or, ir) ...
2223// Description:
2224// Rounds an arbitrary 2d objects. Giving `r` rounds all concave and
2225// convex corners. Giving just `ir` rounds just concave corners.
2226// Giving just `or` rounds convex corners. Giving both `ir` and `or`
2227// can let you round to different radii for concave and convex corners.
2228// The 2d object must not have any parts narrower than twice the `or`
2229// radius. Such parts will disappear.
2230// Arguments:
2231// r = Radius to round all concave and convex corners to.
2232// or = Radius to round only outside (convex) corners to. Use instead of `r`.
2233// ir = Radius to round/fillet only inside (concave) corners to. Use instead of `r`.
2234// Examples(2D):
2235// round2d(r=10) {square([40,100], center=true); square([100,40], center=true);}
2236// round2d(or=10) {square([40,100], center=true); square([100,40], center=true);}
2237// round2d(ir=10) {square([40,100], center=true); square([100,40], center=true);}
2238// round2d(or=16,ir=8) {square([40,100], center=true); square([100,40], center=true);}
2239module round2d(r, or, ir)
2240{
2241 or = get_radius(r1=or, r=r, dflt=0);
2242 ir = get_radius(r1=ir, r=r, dflt=0);
2243 offset(or) offset(-ir-or) offset(delta=ir) children();
2244}
2245
2246
2247// Module: shell2d()
2248// Usage:
2249// shell2d(thickness, [or], [ir], [fill], [round])
2250// Description:
2251// Creates a hollow shell from 2d children, with optional rounding.
2252// Arguments:
2253// thickness = Thickness of the shell. Positive to expand outward, negative to shrink inward, or a two-element list to do both.
2254// or = Radius to round convex corners/pointy bits on the outside of the shell.
2255// ir = Radius to round/fillet concave corners on the outside of the shell.
2256// round = Radius to round convex corners/pointy bits on the inside of the shell.
2257// fill = Radius to round/fillet concave corners on the inside of the shell.
2258// Examples(2D):
2259// shell2d(10) {square([40,100], center=true); square([100,40], center=true);}
2260// shell2d(-10) {square([40,100], center=true); square([100,40], center=true);}
2261// shell2d([-10,10]) {square([40,100], center=true); square([100,40], center=true);}
2262// shell2d(10,or=10) {square([40,100], center=true); square([100,40], center=true);}
2263// shell2d(10,ir=10) {square([40,100], center=true); square([100,40], center=true);}
2264// shell2d(10,round=10) {square([40,100], center=true); square([100,40], center=true);}
2265// shell2d(10,fill=10) {square([40,100], center=true); square([100,40], center=true);}
2266// shell2d(8,or=16,ir=8,round=16,fill=8) {square([40,100], center=true); square([100,40], center=true);}
2267module shell2d(thickness, or=0, ir=0, fill=0, round=0)
2268{
2269 thickness = is_scalar(thickness)? (
2270 thickness<0? [thickness,0] : [0,thickness]
2271 ) : (thickness[0]>thickness[1])? (
2272 [thickness[1],thickness[0]]
2273 ) : thickness;
2274 difference() {
2275 round2d(or=or,ir=ir)
2276 offset(delta=thickness[1])
2277 children();
2278 round2d(or=fill,ir=round)
2279 offset(delta=thickness[0])
2280 children();
2281 }
2282}
2283
2284
2285
2286//////////////////////////////////////////////////////////////////////
2287// Section: Miscellaneous
2288//////////////////////////////////////////////////////////////////////
2289
2290
2291// Module: orient_and_align()
2292//
2293// Description:
2294// Takes a vertically oriented shape, and re-orients and aligns it.
2295// This is useful for making a custom shape available in various
2296// orientations and alignments without extra translate()s and rotate()s.
2297// Children should be vertically (Z-axis) oriented, and centered.
2298// Non-extremity alignment points should be named via the `alignments` arg.
2299// Named alignments, as well as `ALIGN_NEG`/`ALIGN_POS` are aligned pre-rotation.
2300//
2301// Usage:
2302// orient_and_align(size, [orient], [align], [center], [noncentered], [orig_orient], [orig_align], [alignments]) ...
2303//
2304// Arguments:
2305// size = The size of the part.
2306// orient = The axis to align to. Use ORIENT_ constants from constants.scad
2307// align = The side of the origin the part should be aligned with.
2308// center = If given, overrides `align`. If true, centers vertically. If false, `align` will be set to the value in `noncentered`.
2309// noncentered = The value to set `align` to if `center` == `false`. Default: `V_UP`.
2310// orig_orient = The original orientation of the part. Default: `ORIENT_Z`.
2311// orig_align = The original alignment of the part. Default: `V_CENTER`.
2312// alignments = A list of `["name", [X,Y,Z]]` alignment-label/offset pairs.
2313//
2314// Example:
2315// #cylinder(d=5, h=10);
2316// orient_and_align([5,5,10], orient=ORIENT_Y, align=V_BACK, orig_align=V_UP) cylinder(d=5, h=10);
2317module orient_and_align(
2318 size=undef, orient=ORIENT_Z, align=V_CENTER,
2319 center=undef, noncentered=ALIGN_POS,
2320 orig_orient=ORIENT_Z, orig_align=V_CENTER,
2321 alignments=[]
2322) {
2323 algn = is_def(center)? (center? V_CENTER : noncentered) : align;
2324 if (orig_align != V_CENTER) {
2325 orient_and_align(size=size, orient=orient, align=algn) {
2326 translate(vmul(size/2, -orig_align)) children();
2327 }
2328 } else if (orig_orient != ORIENT_Z) {
2329 rotsize = (
2330 (orig_orient==ORIENT_X)? [size[1], size[2], size[0]] :
2331 (orig_orient==ORIENT_Y)? [size[0], size[2], size[1]] :
2332 vabs(rotate_points3d([size], orig_orient, reverse=true)[0])
2333 );
2334 orient_and_align(size=rotsize, orient=orient, align=algn) {
2335 rot(orig_orient,reverse=true) children();
2336 }
2337 } else if (is_scalar(algn)) {
2338 // If align is a number and not a vector, then translate PRE-rotation.
2339 orient_and_align(size=size, orient=orient) {
2340 translate(vmul(size/2, algn*V_UP)) children();
2341 }
2342 } else if (is_str(algn)) {
2343 // If align is a string, look for an alignments label that matches.
2344 found = search([algn], alignments, num_returns_per_match=1);
2345 if (found != [[]]) {
2346 orient_and_align(size=size, orient=orient) {
2347 idx = found[0];
2348 delta = alignments[idx][1];
2349 translate(-delta) children();
2350 }
2351 } else {
2352 assertion(1==0, str("Alignment label '", algn, "' is not known.", (alignments? str(" Try one of ", [for (v=alignments) v[0]], ".") : "")));
2353 }
2354 } else if (orient != ORIENT_Z) {
2355 rotsize = (
2356 (orient==ORIENT_X)? [size[2], size[0], size[1]] :
2357 (orient==ORIENT_Y)? [size[0], size[2], size[1]] :
2358 vabs(rotate_points3d([size], orient)[0])
2359 );
2360 orient_and_align(size=rotsize, align=algn) {
2361 rotate(orient) children();
2362 }
2363 } else if (is_def(algn) && algn != [0,0,0]) {
2364 translate(vmul(size/2, algn)) children();
2365 } else {
2366 children();
2367 }
2368}
2369
2370
2371
2372// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap